/*
 * @Author: lei
 * @Description: IO复用之poll示例
 * @FilePath: /io/poll.cpp
 */
// g++ -o poll poll.cpp -std=c++11 -g
#include <arpa/inet.h>   // inet_ntoa
#include <fcntl.h>       //fcntl
#include <netinet/in.h>  //socket地址类型
#include <string.h>      //bzero
#include <sys/poll.h>    //select
#include <sys/socket.h>  //socket基础api
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <unordered_set>
#include <vector>
using namespace std;

const int N = 2048;

#define exit_if(r, ...)                                            \
  if (r) {                                                         \
    printf(__VA_ARGS__);                                           \
    printf("error no: %d error msg %s\n", errno, strerror(errno)); \
    exit(1);                                                       \
  }

class EchoServer {
 public:
  using PollList = vector<pollfd>;
  // socket创建、bind、listen
  EchoServer(int ser_port) : ser_port_(ser_port) {
    struct sockaddr_in seraddr;
    bzero(&seraddr, sizeof seraddr);
    seraddr.sin_addr.s_addr = INADDR_ANY;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(ser_port);
    ser_fd_ = socket(AF_INET, SOCK_STREAM, 0);
    exit_if(ser_fd_ == -1, "socket() error");
    SetNonBlock(ser_fd_);
    SetReuse(ser_fd_);
    exit_if(::bind(ser_fd_, (const sockaddr*)&seraddr,
                   (socklen_t)sizeof seraddr) == -1,
            "bind() error");
    exit_if(listen(ser_fd_, 100) == -1, "listen() error");
  }
  ~EchoServer() {
    close(ser_fd_);
    for (auto& fd : connfds_) close(fd);
  }

  void Loop() {
    pollfd listen_pollfd;
    bzero(&listen_pollfd, sizeof listen_pollfd);
    listen_pollfd.fd = ser_fd_;
    listen_pollfd.events |= POLLIN;
    fds_.push_back(listen_pollfd);

    for (;;) {
      int size = fds_.size(), nready;
      nready = poll(&*fds_.begin(), size, -1);
      // for (auto& t : connfds_) {
      //   cout << t << " ";
      // }
      if (nready == 0) {
        cout << 5 << "s内暂无事件发生" << endl;
        continue;
      } else if (nready == -1) {
        exit_if(1, "select()错误");
      }
      for (PollList::iterator it = fds_.begin(); it != fds_.end(); it++) {
        int fd = it->fd;
        if (nready == 0)
          break;
        else if (fd == ser_fd_ && (it->revents & POLLIN)) {
          HandleConnection(it, true);
          nready--;
        } else if (it->revents & POLLIN) {
          HandleRead(it);
          nready--;
        } else if (it->revents & POLLOUT) {
          HandleWrite(it);
          nready--;
        }
        // 错误一：不是所有情况都要 nready--;
      }
    }
  }

 private:
  void SetReuse(int fd) {
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, (socklen_t)sizeof reuse);
  }
  void SetNonBlock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    exit_if(flags < 0, "fcntl failed");
    int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    exit_if(r < 0, "fcntl failed");
  }

  // 处理连接建立断开事件
  int HandleConnection(PollList::iterator& it, int flag) {
    int ret = -1;
    if (flag) {  // 连接建立
      struct sockaddr_in cliaddr;
      socklen_t addrlen = sizeof cliaddr;
      bzero(&cliaddr, sizeof cliaddr);
      pollfd temp;
      bzero(&temp, sizeof temp);  // 错误二：要清空，不然里面是垃圾值
      ret = accept(it->fd, (struct sockaddr*)&cliaddr, &addrlen);
      exit_if(ret == -1, "accept()错误");
      temp.events |= POLLIN;
      temp.fd = ret;
      fds_.push_back(temp);
      connfds_.insert(ret);
      printf("accept a connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr),
             cliaddr.sin_port);
    } else {  // 连接断开
      close(it->fd);
      fds_.erase(it);
      connfds_.erase(ret);
      cout << "客户端断开，fd=" << it->fd << endl;
    }
    return ret;
  }

  // 处理数据读事件
  void HandleRead(PollList::iterator& it) {
    bzero(buf_recv_, sizeof buf_recv_);
    int len = recv(it->fd, buf_recv_, sizeof buf_recv_, 0);
    exit_if(len == -1, "read() error");
    if (len == 0)  //*********
    {
      HandleConnection(it, false);
      return;
    }
    cout << "read " << len << " bytes from fd " << it->fd << ":" << buf_recv_
         << endl;  //*****telnet的问题，/r/n
    std::copy(buf_recv_, buf_recv_ + N, buf_send_);
    it->events |= POLLOUT;  //有了这一行，才会触发POLLOUT事件；
  }
  // 处理数据写事件
  void HandleWrite(PollList::iterator& it) {
    send(it->fd, buf_send_, sizeof buf_send_, 0);
    it->events &= ~POLLOUT;
    bzero(buf_send_, sizeof buf_send_);
    // 不手动将POLLOUT事件取消，则revents会一直检测到，从而重复发送
  }

 private:
  PollList fds_;
  char buf_send_[N];
  char buf_recv_[N];
  int ser_port_;
  int ser_fd_;
  unordered_set<int> connfds_;
};

int main() {
  EchoServer echo_server(10005);
  echo_server.Loop();
  return 0;
}
// TODO:支持更多POLL事件